// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/ozone/platform/wayland/fake_server.h"

#include <sys/socket.h>
#include <wayland-server.h>
#include <xdg-shell-unstable-v5-server-protocol.h>

#include "base/bind.h"
#include "base/files/scoped_file.h"
#include "base/strings/string_number_conversions.h"

namespace wl {
namespace {

    const uint32_t kCompositorVersion = 4;
    const uint32_t kXdgShellVersion = 1;

    void DestroyResource(wl_client* client, wl_resource* resource)
    {
        wl_resource_destroy(resource);
    }

    // wl_compositor

    void CreateSurface(wl_client* client, wl_resource* resource, uint32_t id)
    {
        auto compositor = static_cast<MockCompositor*>(wl_resource_get_user_data(resource));
        wl_resource* surface_resource = wl_resource_create(
            client, &wl_surface_interface, wl_resource_get_version(resource), id);
        if (!surface_resource) {
            wl_client_post_no_memory(client);
            return;
        }
        compositor->AddSurface(make_scoped_ptr(new MockSurface(surface_resource)));
    }

    const struct wl_compositor_interface compositor_impl = {
        &CreateSurface, // create_surface
        nullptr, // create_region
    };

    // wl_surface

    void Attach(wl_client* client,
        wl_resource* resource,
        wl_resource* buffer_resource,
        int32_t x,
        int32_t y)
    {
        static_cast<MockSurface*>(wl_resource_get_user_data(resource))
            ->Attach(buffer_resource, x, y);
    }

    void Damage(wl_client* client,
        wl_resource* resource,
        int32_t x,
        int32_t y,
        int32_t width,
        int32_t height)
    {
        static_cast<MockSurface*>(wl_resource_get_user_data(resource))
            ->Damage(x, y, width, height);
    }

    void Commit(wl_client* client, wl_resource* resource)
    {
        static_cast<MockSurface*>(wl_resource_get_user_data(resource))->Commit();
    }

    const struct wl_surface_interface surface_impl = {
        &DestroyResource, // destroy
        &Attach, // attach
        &Damage, // damage
        nullptr, // frame
        nullptr, // set_opaque_region
        nullptr, // set_input_region
        &Commit, // commit
        nullptr, // set_buffer_transform
        nullptr, // set_buffer_scale
        nullptr, // damage_buffer
    };

    // xdg_shell

    void UseUnstableVersion(wl_client* client,
        wl_resource* resource,
        int32_t version)
    {
        static_cast<MockXdgShell*>(wl_resource_get_user_data(resource))
            ->UseUnstableVersion(version);
    }

    void GetXdgSurface(wl_client* client,
        wl_resource* resource,
        uint32_t id,
        wl_resource* surface_resource)
    {
        auto surface = static_cast<MockSurface*>(wl_resource_get_user_data(surface_resource));
        if (surface->xdg_surface) {
            wl_resource_post_error(resource, XDG_SHELL_ERROR_ROLE,
                "surface already has a role");
            return;
        }
        wl_resource* xdg_surface_resource = wl_resource_create(
            client, &xdg_surface_interface, wl_resource_get_version(resource), id);
        if (!xdg_surface_resource) {
            wl_client_post_no_memory(client);
            return;
        }
        surface->xdg_surface.reset(new MockXdgSurface(xdg_surface_resource));
    }

    void Pong(wl_client* client, wl_resource* resource, uint32_t serial)
    {
        static_cast<MockXdgShell*>(wl_resource_get_user_data(resource))->Pong(serial);
    }

    const struct xdg_shell_interface xdg_shell_impl = {
        &DestroyResource, // destroy
        &UseUnstableVersion, // use_unstable_version
        &GetXdgSurface, // get_xdg_surface
        nullptr, // get_xdg_popup
        &Pong, // pong
    };

    // xdg_surface

    void SetTitle(wl_client* client, wl_resource* resource, const char* title)
    {
        static_cast<MockXdgSurface*>(wl_resource_get_user_data(resource))
            ->SetTitle(title);
    }

    void SetAppId(wl_client* client, wl_resource* resource, const char* app_id)
    {
        static_cast<MockXdgSurface*>(wl_resource_get_user_data(resource))
            ->SetAppId(app_id);
    }

    void AckConfigure(wl_client* client, wl_resource* resource, uint32_t serial)
    {
        static_cast<MockXdgSurface*>(wl_resource_get_user_data(resource))
            ->AckConfigure(serial);
    }

    void SetMaximized(wl_client* client, wl_resource* resource)
    {
        static_cast<MockXdgSurface*>(wl_resource_get_user_data(resource))
            ->SetMaximized();
    }

    void UnsetMaximized(wl_client* client, wl_resource* resource)
    {
        static_cast<MockXdgSurface*>(wl_resource_get_user_data(resource))
            ->UnsetMaximized();
    }

    void SetMinimized(wl_client* client, wl_resource* resource)
    {
        static_cast<MockXdgSurface*>(wl_resource_get_user_data(resource))
            ->SetMinimized();
    }

    const struct xdg_surface_interface xdg_surface_impl = {
        &DestroyResource, // destroy
        nullptr, // set_parent
        &SetTitle, // set_title
        &SetAppId, // set_app_id
        nullptr, // show_window_menu
        nullptr, // move
        nullptr, // resize
        &AckConfigure, // ack_configure
        nullptr, // set_window_geometry
        &SetMaximized, // set_maximized
        &UnsetMaximized, // set_unmaximized
        nullptr, // set_fullscreen
        nullptr, // unset_fullscreen
        &SetMinimized, // set_minimized
    };

} // namespace

ServerObject::ServerObject(wl_resource* resource)
    : resource_(resource)
{
}

ServerObject::~ServerObject()
{
    if (resource_)
        wl_resource_destroy(resource_);
}

// static
void ServerObject::OnResourceDestroyed(wl_resource* resource)
{
    auto obj = static_cast<ServerObject*>(wl_resource_get_user_data(resource));
    obj->resource_ = nullptr;
}

MockXdgSurface::MockXdgSurface(wl_resource* resource)
    : ServerObject(resource)
{
    wl_resource_set_implementation(resource, &xdg_surface_impl, this,
        &ServerObject::OnResourceDestroyed);
}

MockXdgSurface::~MockXdgSurface() { }

MockSurface::MockSurface(wl_resource* resource)
    : ServerObject(resource)
{
    wl_resource_set_implementation(resource, &surface_impl, this,
        &ServerObject::OnResourceDestroyed);
}

MockSurface::~MockSurface()
{
    if (xdg_surface && xdg_surface->resource())
        wl_resource_destroy(xdg_surface->resource());
}

MockSurface* MockSurface::FromResource(wl_resource* resource)
{
    if (!wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl))
        return nullptr;
    return static_cast<MockSurface*>(wl_resource_get_user_data(resource));
}

void GlobalDeleter::operator()(wl_global* global)
{
    wl_global_destroy(global);
}

Global::Global(const wl_interface* interface,
    const void* implementation,
    uint32_t version)
    : interface_(interface)
    , implementation_(implementation)
    , version_(version)
{
}

Global::~Global() { }

bool Global::Initialize(wl_display* display)
{
    global_.reset(wl_global_create(display, interface_, version_, this, &Bind));
    return global_;
}

// static
void Global::Bind(wl_client* client,
    void* data,
    uint32_t version,
    uint32_t id)
{
    auto global = static_cast<Global*>(data);
    wl_resource* resource = wl_resource_create(
        client, global->interface_, std::min(version, global->version_), id);
    if (!resource) {
        wl_client_post_no_memory(client);
        return;
    }
    if (!global->resource_)
        global->resource_ = resource;
    wl_resource_set_implementation(resource, global->implementation_, global,
        &Global::OnResourceDestroyed);
}

// static
void Global::OnResourceDestroyed(wl_resource* resource)
{
    auto global = static_cast<Global*>(wl_resource_get_user_data(resource));
    if (global->resource_ == resource)
        global->resource_ = nullptr;
}

MockCompositor::MockCompositor()
    : Global(&wl_compositor_interface, &compositor_impl, kCompositorVersion)
{
}

MockCompositor::~MockCompositor() { }

void MockCompositor::AddSurface(scoped_ptr<MockSurface> surface)
{
    surfaces_.push_back(std::move(surface));
}

MockXdgShell::MockXdgShell()
    : Global(&xdg_shell_interface, &xdg_shell_impl, kXdgShellVersion)
{
}

MockXdgShell::~MockXdgShell() { }

void DisplayDeleter::operator()(wl_display* display)
{
    wl_display_destroy(display);
}

FakeServer::FakeServer()
    : Thread("fake_wayland_server")
    , pause_event_(false, false)
    , resume_event_(false, false)
{
}

FakeServer::~FakeServer()
{
    Stop();
}

bool FakeServer::Start()
{
    display_.reset(wl_display_create());
    if (!display_)
        return false;
    event_loop_ = wl_display_get_event_loop(display_.get());

    int fd[2];
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fd) < 0)
        return false;
    base::ScopedFD server_fd(fd[0]);
    base::ScopedFD client_fd(fd[1]);

    if (wl_display_init_shm(display_.get()) < 0)
        return false;
    if (!compositor_.Initialize(display_.get()))
        return false;
    if (!xdg_shell_.Initialize(display_.get()))
        return false;

    client_ = wl_client_create(display_.get(), server_fd.get());
    if (!client_)
        return false;
    (void)server_fd.release();

    base::Thread::Options options;
    options.message_pump_factory = base::Bind(&FakeServer::CreateMessagePump, base::Unretained(this));
    if (!base::Thread::StartWithOptions(options))
        return false;

    setenv("WAYLAND_SOCKET", base::UintToString(client_fd.release()).c_str(), 1);

    return true;
}

void FakeServer::Flush()
{
    wl_display_flush_clients(display_.get());
}

void FakeServer::Pause()
{
    task_runner()->PostTask(
        FROM_HERE, base::Bind(&FakeServer::DoPause, base::Unretained(this)));
    pause_event_.Wait();
}

void FakeServer::Resume()
{
    resume_event_.Signal();
}

void FakeServer::DoPause()
{
    pause_event_.Signal();
    resume_event_.Wait();
}

scoped_ptr<base::MessagePump> FakeServer::CreateMessagePump()
{
    auto pump = make_scoped_ptr(new base::MessagePumpLibevent);
    pump->WatchFileDescriptor(wl_event_loop_get_fd(event_loop_), true,
        base::MessagePumpLibevent::WATCH_READ, &controller_,
        this);
    return std::move(pump);
}

void FakeServer::OnFileCanReadWithoutBlocking(int fd)
{
    wl_event_loop_dispatch(event_loop_, 0);
    wl_display_flush_clients(display_.get());
}

void FakeServer::OnFileCanWriteWithoutBlocking(int fd) { }

} // namespace wl
